/*
 * SoftwareSAR - 1.0
 */
package br.com.cpqd.ssar.geradormalha;

import br.com.cpqd.ssar.geradormalha.gui.InterfaceRenderizacaoMalha;
import br.com.cpqd.ssar.utilitarias.Geometria;
import br.com.cpqd.ssar.utilitarias.Triangulo;
import java.util.Arrays;

/**
 * Gerador de Malha para o Método FDTD
 * @author Rafael Buck
 * @version 1.2
 */
public class GeradorMalhaFDTD implements InterfaceRenderizacaoMalha {

    private int STD_BUFFER = 1024;
    private int nx;         // numero de elementos em x
    private int ny;         // numero de elementos em y
    private int nz;         // numero de elementos em z
    private float dx;       // delta x
    private float dy;       // delta y
    private float dz;       // delta z
    /**
     * Coordenadas da Cena
     */
    private float xMin;
    private float xMax;
    private float yMin;
    private float yMax;
    private float zMin;
    private float zMax;

    /**
     * Quantidade de Camadas PML
     */
    private int pmlxi;
    private int pmlxf;
    private int pmlyi;
    private int pmlyf;
    private int pmlzi;
    private int pmlzf;



    /**
     * Malha 3D onde cada elemento representa um material
     */
    private int[][][] malha;

    /**
     * Método Construtor
     * @param importadorMalha ImportadorMalhaFDTD
     * @param dx float
     * @param dy float
     * @param dz float
     * @param xMin float
     * @param xMax float
     * @param yMin float
     * @param yMax float
     * @param zMin float
     * @param zMax float
     * @exception Exception
     */
    public GeradorMalhaFDTD(ImportadorMalhaFDTD importadorMalha, int dx, int dy, int dz, float xMin, float xMax, float yMin, float yMax, float zMin, float zMax) throws Exception {
        // Coordenadas máximas e mínimas
        this.xMin = xMin;
        this.xMax = xMax;
        this.yMin = yMin;
        this.yMax = yMax;
        this.zMin = zMin;
        this.zMax = zMax;

        // dimensões
        nx = Math.round(Math.abs((xMax - xMin) / dx));
        ny = Math.round(Math.abs((yMax - yMin) / dy));
        nz = Math.round(Math.abs((zMax - zMin) / dz));

        malha = new int[nx][ny][nz];
        for (int i = 0; i < nx; i++) {
            for (int j = 0; j < ny; j++) {
                for (int k = 0; k < nz; k++) {
                    malha[i][j][k] = 1;
                }
            }
        }

        int iInicial = 0;
        int jInicial = 0;
        int kInicial = 0;

        float xMinMalha = 0;
        float yMinMalha = 0;
        float zMinMalha = 0;

        float xMinCena = 0;
        float yMinCena = 0;
        float zMinCena = 0;

        // Verificar se não precisa diminuir depois
        while (xMinCena < xMinMalha) {
            iInicial++;
            xMinCena = iInicial * xMinCena;
        }
        while (yMinCena < yMinMalha) {
            jInicial++;
            yMinCena = jInicial * yMinCena;
        }
        while (zMinCena < zMinMalha) {
            kInicial++;
            zMinCena = kInicial * zMinCena;
        }
        // length.nx do importador
        for (int i = 0; i < nx; i++) {
            // length.ny do importador
            for (int j = 0; j < ny; j++) {
                // length.nz do importador
                for (int k = 0; k < nz; k++) {
                    // Material do Importador
                    malha[i + iInicial][j + jInicial][k + kInicial] = 1;
                }
            }
        }

    }

    /**
     * Método Construtor Sobrecarregado
     * @param nx int
     * @param ny int
     * @param nz int
     * @param xMin float
     * @param xMax float
     * @param yMin float
     * @param yMax float
     * @param zMin float
     * @param zMax float
     */
    public GeradorMalhaFDTD(int nx, int ny, int nz, float xMin, float xMax, float yMin, float yMax, float zMin, float zMax) {
        // Dimensões
        this.nx = nx;
        this.ny = ny;
        this.nz = nz;

        // Coordenadas máximas e mínimas
        this.xMin = xMin;
        this.xMax = xMax;
        this.yMin = yMin;
        this.yMax = yMax;
        this.zMin = zMin;
        this.zMax = zMax;

        // deltas
        dx = Math.abs((xMax - xMin) / nx);
        dy = Math.abs((yMax - yMin) / ny);
        dz = Math.abs((zMax - zMin) / nz);

        // Tenta alocar memória à malha
        try {
            malha = new int[nx][ny][nz];
            for (int i = 0; i < nx; i++) {
                for (int j = 0; j < ny; j++) {
                    for (int k = 0; k < nz; k++) {
                        malha[i][j][k] = 1;
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(-1);
        }
    }

    /**
     * Método Construtor Sobrecarregado (para PML)
     * @param nx int
     * @param ny int
     * @param nz int
     * @param nxiPML int número de cubos que irão compor a PML do eixo x (inicial)
     * @param nxfPML int número de cubos que irão compor a PML do eixo x (final)
     * @param nyiPML int número de cubos que irão compor a PML do eixo y (inicial)
     * @param nyfPML int número de cubos que irão compor a PML do eixo y (final)
     * @param nziPML int número de cubos que irão compor a PML do eixo z (inicial)
     * @param nzfPML int número de cubos que irão compor a PML do eixo z (final)
     * @param xMin float
     * @param xMax float
     * @param yMin float
     * @param yMax float
     * @param zMin float
     * @param zMax float
     */
    public GeradorMalhaFDTD(int tnx, int tny, int tnz, float xMin, float xMax, float yMin, float yMax, float zMin, float zMax, int nxiPML, int nxfPML, int nyiPML, int nyfPML, int nziPML, int nzfPML) {
        // Dimensões
        this.nx = tnx;
        this.ny = tny;
        this.nz = tnz;

        this.pmlxi = nxiPML;
        this.pmlxf = nxfPML;
        this.pmlyi = nyiPML;
        this.pmlyf = nyfPML;
        this.pmlzi = nziPML;
        this.pmlzf = nzfPML;


        // Coordenadas máximas e mínimas
        this.xMin = xMin;
        this.xMax = xMax;
        this.yMin = yMin;
        this.yMax = yMax;
        this.zMin = zMin;
        this.zMax = zMax;

        // deltas
        dx = Math.abs((xMax - xMin) / nx);
        dy = Math.abs((yMax - yMin) / ny);
        dz = Math.abs((zMax - zMin) / nz);

        // Tenta alocar memória à malha
        try {
            malha = new int[this.nx][this.ny][this.nz];
            for (int i = 0; i < nx; i++) {
                for (int j = 0; j < ny; j++) {
                    for (int k = 0; k < nz; k++) {
                        malha[i][j][k] = 1;
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(-1);
        }
    }

    /**
     * Gera Malha 3D para o método FDTD
     * @param geometria Geometria[]
     */
    public void gerarMalhaFDTD3D(Geometria[] geometria) {
        int i, j, k, n, m, p;               // variaveis de contagem
        int contFaces;                      // numero de faces seccionadas
        int contEdges;                      // numero de arestas cortadas
        float z;                            // z do plano (medio)
        float y;                            // y do raio (medio)
        float zsup, zinf;                   // z superior, z inferior a z'
        float ysup, yinf;                   // y inferior, y superior a y'
        int msup, minf;                     // Ymsup > y' e Yminf < y'
        int[] selectedFaces;                // faces que seccionam o plano
        int[] selectedEdges;                // arestas que sao cortadas pelo raio
        float[] edgeXc;                     // Xc do corte para cada aresta seccionada
        Triangulo tempTriangle = new Triangulo();// triangulo temporario
        Edge tempEdge;                      // aresta temporaria
        boolean flag1 = false;              // flag que indica interseccao/corte (ponto superior)
        boolean flag2 = false;              // flag que indica interseccao/corte (ponto inferior)
        boolean flag3 = false;              // flag que indica um ponto igual a reta de corte
        boolean flag4 = false;              // flag que indica dois pontos iguais a reta de corte
        boolean in = false;                 // dentro do polígono

        Edge[] polygon;                     // poligono definido pela seccao em z = z'

        float Xc, X1, Y1, X2, Y2;           // Variaveis da equacao (1)
        float x;

        selectedFaces = new int[STD_BUFFER];// Inicializa buffer que guarda as faces selecionadas
        selectedEdges = new int[STD_BUFFER];// Inicializa buffer que guarda as arestas selecionadas
        edgeXc = new float[STD_BUFFER];     // Inicializa buffer que guarda os pontos de intersecção

        float iy = 0.05f * dy;              // incremento em y (singularidade)
        float iz = 0.05f * dz;              // incremento em z (singularidade)

        minf = msup = 0;

        for (int t = 0; t < geometria.length; t++) {

            System.out.println("GeradorMalhaFDTD: nome = " + geometria[t].getNome());
            System.out.println("GeradorMalhaFDTD: min: " + geometria[t].getXMin() + " " + geometria[t].getYMin() + " " + geometria[t].getZMin());
            System.out.println("GeradorMalhaFDTD: max: " + geometria[t].getXMax() + " " + geometria[t].getYMax() + " " + geometria[t].getZMax());

            for (k = 0; k < nz; k++) {
                contFaces = 0;
                z = (zMin + k * dz) + (dz * 0.5f);                                         // plano em z = dz/2       
                if ((geometria[t].getZMin() <= z) & (geometria[t].getZMax() >= z)) {       // objeto pelo menos cruza o eixo z                        
                    for (n = 0; n < geometria[t].getListaTriangulos().length; n++) {       // varre os n elementos da geometria
//                        tempTriangle = (Polygon) obj.getElementAt(n); // n-esimo triangulo                        
                        tempTriangle.setup(geometria[t].getListaTriangulos(n));            // n-esimo triangulo 

//                            System.out.print("x1(" + objecto + ") + = " + tempTriangle.getVertex(0).getX());
//                            System.out.print(" y1(" + objecto + ") + = " + tempTriangle.getVertex(0).getY());
//                            System.out.println("z1(" + objecto + ") + = "  + tempTriangle.getVertex(0).getZ());
//                            
//                            System.out.print("x2(" + objecto + ") + = "  + tempTriangle.getVertex(1).getX());
//                            System.out.print(" y2(" + objecto + ") + = "  + tempTriangle.getVertex(1).getY());
//                            System.out.println(" z2(" + objecto + ") + = "  + tempTriangle.getVertex(1).getZ());
//                            
//                            System.out.print("x3(" + objecto + ") + = "  + tempTriangle.getVertex(2).getX());
//                            System.out.print(" y3(" + objecto + ") + = "  + tempTriangle.getVertex(2).getY());
//                            System.out.println(" z3(" + objecto + ") + = "  + tempTriangle.getVertex(2).getZ());
//                            
//                            System.out.println();

                        for (m = 0; m < 3; m++) {                       // ajustado para testar 3 vertices
                            if (tempTriangle.getVertice(m).getCoordenada(2) > z) {
                                flag1 = true;
                            } else if (tempTriangle.getVertice(m).getCoordenada(2) < z) {
                                flag2 = true;
                            } else {
                                // algum ponto z = z' (singularidade)
                                if (flag3) {
                                    // alguma aresta z = z' (singularidade)
                                    flag4 = true;
//                                    tempTriangle.getVertex(m).setZ(z + iz);  // incrementa o m-esimo vertice da aresta (sobe aresta)
                                    tempTriangle.getVertice(m).setCoordenada(2, z + iz); // incrementa o m-esimo vertice da aresta (sobe aresta)
                                } else {
                                    flag3 = true;
//                                    tempTriangle.getVertex(m).setZ(z + iz);  // incrementa o m-esimo vertice da aresta (sobe aresta)
                                    tempTriangle.getVertice(m).setCoordenada(2, z + iz); // incrementa o m-esimo vertice da aresta (sobe aresta)
                                }
                            }
                            if (flag3 & (m == 2)) {
                                // repete procedimento pois houve singularidade
                                m = -1;     // to repeat m = 0,1
                                flag1 = false;
                                flag2 = false;
                                flag3 = false;
                                flag4 = false;
                            }
                        }
                        if (flag1 & flag2) {                            // tem um ponto em z'>z e outro em z'<z
                            if (contFaces < selectedFaces.length) {
                                selectedFaces[contFaces] = n;
                            } else {
                                selectedFaces = this.resizeBufferInt(selectedFaces);
                                selectedFaces[contFaces] = n;
                            }
                            contFaces++;
                        }
                        flag1 = false;
                        flag2 = false;
                        flag3 = false;
                        flag4 = false;
                    }
                }

                polygon = new Edge[contFaces];                          // instancia o numero de arestas (= numero de faces)
                for (j = 0; j < contFaces; j++) {                       // cria cada aresta do poligono
//                    polygon[j] = new Edge((Polygon) obj.getElementAt(selectedFaces[j]), z);
                    polygon[j] = new Edge(geometria[t].getListaTriangulos(selectedFaces[j]), z);
                }

                for (j = 0; j < ny; j++) {
                    contEdges = 0;
                    y = (yMin + j * dy) + (dy * 0.5f);                  // plano em y = dy/2
                    for (n = 0; n < polygon.length; n++) {              // varre as n arestas
                        tempEdge = polygon[n];                          // n-esima aresta
                        for (m = 0; m < 2; m++) {                       // testa 2 vertices da aresta
                            if (tempEdge.getElementAt(m).getCoordenada(1) > y) {
                                flag1 = true;
                                msup = m;
                            } else if (tempEdge.getElementAt(m).getCoordenada(1) < y) {
                                flag2 = true;
                                minf = m;
                            } else {
                                // y1 = y' (singularidade)
                                if (flag3) {
                                    // y1 = y2 = y' (singularidade)
                                    flag4 = true;
//                                    tempEdge.getElementAt(m).setY(y + iy);  // incrementa o m-esimo vertice da aresta (sobe aresta)
                                    tempEdge.getElementAt(m).setCoordenada(1, y + iy); // incrementa o m-esimo vertice da aresta (sobe aresta)
                                } else {
                                    flag3 = true;
//                                    tempEdge.getElementAt(m).setY(y + iy);  // incrementa o m-esimo vertice da aresta (sobe vertice)
                                    tempEdge.getElementAt(m).setCoordenada(1, y + iy); // incrementa o m-esimo vertice da aresta (sobe vertice)
                                }
                            }

                            if (flag3 & (m == 1)) {
                                // repete procedimento pois houve singularidade
                                m = -1;     // to repeat m = 0,1
                                flag1 = false;
                                flag2 = false;
                                flag3 = false;
                                flag4 = false;
                            }

                        }

                        if (flag1 & flag2) {                          // tem um ponto em y'>y e outro em y'<y

//                            X1 = tempEdge.getElementAt(minf).getX();
//                            Y1 = tempEdge.getElementAt(minf).getY();
//                            X2 = tempEdge.getElementAt(msup).getX();
//                            Y2 = tempEdge.getElementAt(msup).getY();

                            X1 = tempEdge.getElementAt(minf).getCoordenada(0);
                            Y1 = tempEdge.getElementAt(minf).getCoordenada(1);
                            X2 = tempEdge.getElementAt(msup).getCoordenada(0);
                            Y2 = tempEdge.getElementAt(msup).getCoordenada(1);

                            Xc = X1 + ((y - Y1) * (X2 - X1)) / (Y2 - Y1);

                            if (contEdges < selectedEdges.length) {
                                selectedEdges[contEdges] = n;
                                edgeXc[contEdges] = Xc;
                            } else {
                                selectedFaces = this.resizeBufferInt(selectedFaces);
                                edgeXc = this.resizeBufferFloat(edgeXc);
                                selectedEdges[contEdges] = n;
                                edgeXc[contEdges] = Xc;
                            }
                            contEdges++;
                        }

                        flag1 = false;
                        flag2 = false;
                        flag3 = false;
                        flag4 = false;
                    }

                    Arrays.sort(edgeXc, 0, contEdges);    // ordena o vetor de cortes em X

                    in = false;
                    p = 0;
                    for (i = 0; i < nx; i++) {
//                        x = (this.getXmin() + i * this.getDeltaX()) + (this.getDeltaX() * 0.5f);
                        x = (this.xMin + i * this.dx) + (this.dx * 0.5f);
                        if ((x >= edgeXc[p]) & (contEdges != 0)) {
                            if (p < contEdges) {
                                in = !in;
                                p++;
                            }
                        }
                        if (in) {
                            // objeto
                            //mesh3D[i][j][k] = obj.getMaterial();
                            malha[i][j][k] = geometria[t].getMaterial().getId();
                        } else {
                            // ar
//                            malha[i][j][k] = 1;
                        }

                    }

                }

            }

        }

        int contx, conty, contz;
        int [][][] malhatemp = new int[this.nx+this.pmlxi+this.pmlxf][this.ny+this.pmlyi+this.pmlyf][this.nz+this.pmlzi+this.pmlzf];
        for(contx=0; contx < this.nx+this.pmlxi+this.pmlxf; contx++)
            for(conty=0; conty < this.ny+this.pmlyi+this.pmlyf; conty++)
                for(contz=0; contz < this.nz+this.pmlzi+this.pmlzf; contz++)
                   malhatemp[contx][conty][contz] =1;

        for (contx=0; contx < this.nx; contx++)
            for(conty=0; conty < this.ny; conty++)
                for (contz=0; contz < this.nz; contz++)
                    malhatemp[contx+pmlxi][conty+pmlyi][contz+pmlzi]=malha[contx][conty][contz];
        
        malha =  new int[this.nx][this.ny][this.nz];
        malha = malhatemp;

    }

    /**
     * Realoca Memória para um determinado Buffer
     * @param buffer int[]
     * @return buffer aumentado
     */
    public int[] resizeBufferInt(int[] buffer) {

        int[] newBuffer;

        // aqui tem exception se (buffer.length)*2 > 2^31 - 1 (maximo int > 0 em Java)
        newBuffer = new int[(buffer.length) * 2];
        for (int i = 0; i < buffer.length; i++) {
            newBuffer[i] = buffer[i];
        }
        return newBuffer;

    }

    /**
     * Realoca Memória para um determinado Buffer
     * @param buffer float[]
     * @return buffer aumentado
     */
    public float[] resizeBufferFloat(float[] buffer) {

        float[] newBuffer;

        // aqui tem exception se (buffer.length)*2 > 2^31 - 1 (maximo int > 0 em Java)
        newBuffer = new float[(buffer.length) * 2];
        for (int i = 0; i < buffer.length; i++) {
            newBuffer[i] = buffer[i];
        }

        return newBuffer;
    }

    public int obterMaterialCubo(int i, int j, int k) {
        return malha[i][j][k];
    }

    public float getDx() {
        return dx;
    }

    public float getDy() {
        return dy;
    }

    public float getDz() {
        return dz;
    }

    public int getNx() {
        return nx+pmlxi+pmlxf;
    }

    public int getNy() {
        return ny+pmlyi+pmlyf;
    }

    public int getNz() {
        return nz+pmlzi+pmlzf;
    }

    public float getXMax() {
        return xMax+(pmlxf*dx);
    }

    public float getXMin() {
        return xMin-(pmlxi*dx);
    }

    public float getYMax() {
        return yMax+(pmlyf*dy);
    }

    public float getYMin() {
        return yMin-(pmlyi*dy);
    }

    public float getZMax() {
        return zMax+(pmlzf*dz);
    }

    public float getZMin() {
        return zMin-(pmlzi*dz);
    }
}
